<?php

/**
 * @name APP二维码扫码服务支持
 * @author vipkwd <service@vipkwd.com>
 * @link https://github.com/wxy545812093/vipkwd-phputils
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @copyright The PHP-Tools
 */

declare(strict_types=1);

namespace Vipkwd\Utils\ScanLogin;

use Vipkwd\Utils\Ip as VkIP;
use Vipkwd\Utils\Http;
use Vipkwd\Utils\Crypt;
// use Vipkwd\Utils\Image\Qrcode as VkQrcode;
use Vipkwd\Utils\Color;
use Vipkwd\Utils\Qrcode\QRcdr as PHPQRcdr;
use Vipkwd\Utils\ScanLogin\Enum;
class AppLogin
{

    use Traits;

    private $_options = [];
    private $_request;
    private $_query;
    private $_queryArray = [];

    private $pushTextRaw = '';

    private function __construct($options)
    {
        $this->_request = Http::request();
        $this->_query = $this->_request->query;
        $this->_queryArray = array_values((array) $this->_request->query);
        $this->_queryArray = array_shift($this->_queryArray);
        $this->_options = array_merge([
            "web_pusher_url" => '',
            'salt_key' => '',
        ], $options);
    }

    /**
     * ST1. 生成二维码原始数据
     * 
     * @param string $event 自协商的场景事件类型（如二维码用于后台登录，则可为 "admin-login"）
     * @param array $params 自定义数据 接受参数 clientId,qrcodeId,notify,event,backColor,frontColor
     * @param bool $dataType 响应数据类型 true时返回数组，false时php ob函数自动发送jpeg类型的二进制流
     * @param string $clientId  为socket标识
     * @param string $qrcodeId 手机最后扫码的那一个码ID
     * 
     * @return array|\header
     */
    public function createQrcode(string $event, array $params = [], string $clientId = '', string $qrcodeId = '', bool $dataType = true, string $platform = Enum::QR_PLATFORM_PC)
    {
        $params = array_merge([Enum::FIXED_KEY_CLIENTID => $clientId, 'qrcodeId' => $qrcodeId], $params);
        $params['clientIp'] = VkIP::getClientIp();
        $params[Enum::FIXED_KEY_NOTIFY] = true; //标记APP 扫码完成且事件鉴定通过后需要上报扫码状态（典型使用场景：扫码后台登录二维码后，需要更新 登录页面的码状态为 “已扫码，请在手机上确认”）
        $params[Enum::FIXED_KEY_STATE] = Enum::QR_STATE_WSCAN;
        $qrcodeData = self::createQrcodeData($event, $params, 0, 60000, $platform); //10分钟有效

        $back_color = Color::colorHexFix($params['backColor'] ?? '', "#FFF");
        $front_color = Color::colorHexFix($params['frontColor'] ?? '', "#000");
        unset($params);

        // text	    String	'hello'	二维码内容
        // size	    Number	340	单位是px
        // quality	    String	'L'	二维码解析度L/M/Q/H
        // colorDark	String	'#000000'	黑色二维码
        // colorLight	String	'#ffffff'	白色背景

        $qrcodeData = array_merge($qrcodeData, [
            'expireDate' => date('n月j日', $qrcodeData['timestamp']),
            'text' => str_replace(" ", '+', $qrcodeData['text']),
            'size' => 200,
            'quality' => 'M',
            'colorDark' => $front_color,
            'colorLight' => $back_color,
        ]);
        if ($dataType === true) {
            return $qrcodeData;
        }
        //text 加密后的扫码核心数据
        PHPQRcdr::png($qrcodeData['text'], false, 'M', 4, 2, false, $back_color, $front_color);
        exit;
    }

    /**
     * ST2. 解密APP扫码的结果（含事件标识）
     * 
     * @param string $text 扫描二维码得到的原始加密文本
     * @param string|integer $scanUserId 扫码人的身份标识（通常是user_id）
     * @param string|null $event 约定的事件类型匹配
     * @param \Closure|null $eventCallback 事件匹配时的 执行任务（任务返回布尔结果, false时拒绝本地事件Invoke）
     * 
     * @return array
     */
    public function scanEventInvoke(string $text, string $event = null, $scanUserId = 0, \Closure $eventCallback = null): array
    {
        $data = [ 'scan_state' => 14, 'scan_msg' => '解码失败' ];
        $isEvent = substr($text, 0, 3) == 'ev.';
        $arr = explode('|', $text);
        if ($isEvent) {
            !$event && ($event = substr($arr[0], 3));
            $data = self::decryptFormData($arr[1] ?? '', $event);
            if (!is_array($data)) {
                return [
                    'scan_state' => 13,
                    'scan_msg' => $data,
                ];
            }
            if ($scanUserId) {

                $state = true;
                if ($event == $data[Enum::FIXED_KEY_EVENT_NAME] && $eventCallback && is_callable($eventCallback)) {
                    $state = (bool) $eventCallback($data);
                }
                if ($state === true) {

                    $data['scan_state'] = 0;
                    $data['scan_msg'] = 'ok';

                    $aesDataFlush = false;
                    switch ($data[Enum::FIXED_KEY_STATE]) {
                        case Enum::QR_STATE_CANCEL:
                            $data = $this->scanEventReject($data[Enum::FIXED_KEY_CLIENTID], $data[Enum::FIXED_KEY_QRCODEID], $event, $data, $scanUserId);
                            
                            break;
                        case Enum::QR_STATE_CONFIRM:

                            // 推送(原始)数据, 推送完会自动销毁
                            $this->pushTextRaw = $text;
                            $data = $this->scanEventConfirm($data[Enum::FIXED_KEY_CLIENTID], $data[Enum::FIXED_KEY_QRCODEID], $event, $data, $scanUserId);

                            break;
                        case Enum::QR_STATE_SCANED:
                            $this->scanEventComplete($data[Enum::FIXED_KEY_CLIENTID], $data[Enum::FIXED_KEY_QRCODEID], $event, $data, $scanUserId);

                           //确认授权时，有 【授权】与【拒绝】俩个分支(先写"拒绝"，后写"接受"， 以"接受" 参数 覆盖统一返回的数据格式)
                           $_event = $data[Enum::FIXED_KEY_EVENT_NAME];
                           $data[Enum::FIXED_KEY_EVENT_NAME] .= '-' . Enum::QR_STATE_AUTH_REJECT;
                           $data[Enum::FIXED_KEY_STATE] = Enum::QR_STATE_CANCEL;
                           $_tmp = self::createQrcodeData($data[Enum::FIXED_KEY_EVENT_NAME], $data, 0, Enum::QR_DATA_DURATION_SECONDS, $data[Enum::FIXED_KEY_PLATFORM]);
                           $data['text2'] = $_tmp['text'];

                           //覆盖参数
                           $data[Enum::FIXED_KEY_EVENT_NAME] =  $_event .'-' . Enum::QR_STATE_AUTH_CONFIRM; //(注意是: 等于 不是: 加等于)
                           $data[Enum::FIXED_KEY_STATE] = Enum::QR_STATE_CONFIRM;

                            $aesDataFlush = true;
                            break;

                        case Enum::QR_STATE_WSCAN:
                            $data[Enum::FIXED_KEY_UID] = $scanUserId; //变量
                            $data[Enum::FIXED_KEY_EVENT_NAME] .= '-' . Enum::QR_STATE_CONFIRM;
                            $data[Enum::FIXED_KEY_STATE] = Enum::QR_STATE_SCANED;
                            $aesDataFlush = true;
                            break;
                    }
                    if($aesDataFlush === true){
                        // 上面对原始数据已验证通过，二次延长覆盖原始数据的过期时间，防止后续验签时，数据失效;
                        $data[Enum::FIXED_KEY_EXPIRES_TIME] = time() + Enum::QR_DATA_DURATION_SECONDS;
                        $_tmp = self::createQrcodeData($data[Enum::FIXED_KEY_EVENT_NAME], $data, 0, Enum::QR_DATA_DURATION_SECONDS, $data[Enum::FIXED_KEY_PLATFORM]);
                        //下发事件原始数据，以被后续验证事件来源(即是验签的作用)
                        // $data['text'] = Crypt::authcode(json_encode($data), 'ENCODE', '', $Enum::QR_DATA_DURATION_SECONDS);
                        $data['text'] = $_tmp['text'];
                        unset($_tmp);
                    }
                    return $data;
                }
                $data['scan_state'] = 10;
                $data['scan_msg'] = '事件拉起失败';
            } else {
                $data['scan_state'] = 11;
                $data['scan_msg'] = '未能识别扫码用户身份';
            }
        }
        return $data;
    }

    /**
     * ST3.下发“扫码成功”事件
     * 
     * @param string $clientId  为socket标识
     * @param string $qrcodeId 手机最后扫码的那一个码ID
     * @param string $event 约定的事件类型匹配
     * @param array $params  [text,event]
     * @param string|integer $scanUserId 扫码人的身份标识（前序流程已约定值）
     * 
     * @return array|null
     */
    public function scanEventComplete(string $clientId, string $qrcodeId, string $event, array &$params, $scanUserId = 0)
    {

        $result = ['scan_state' => 22, 'scan_msg' => '远程通知失败.'.Enum::QR_STATE_SCANED];
        $data = $params;
        if(!isset($params['__scan_decode__'])){
            $params = array_merge([
                'text' => '',
                Enum::FIXED_KEY_CLIENTID => $clientId,
                Enum::FIXED_KEY_QRCODEID => $qrcodeId,
                Enum::FIXED_KEY_EVENT_NAME => '',
                Enum::FIXED_KEY_UID => 0,
            ], $params);
            $data = self::decryptFormData($params['text'], $params[Enum::FIXED_KEY_EVENT_NAME]);
        }
        if (is_array($data)) {
            //TODO 应该进行 data 与  params 比对;
            if ($params[Enum::FIXED_KEY_EVENT_NAME] && $params[Enum::FIXED_KEY_EVENT_NAME] == $event && $params[Enum::FIXED_KEY_UID] == $scanUserId) {
                //后台发起的扫码，仅通知对应的 码视图 更新为 “已扫码，请在手机上确认”状态

                if (isset($params[Enum::FIXED_KEY_CLIENTID]) && $data[Enum::FIXED_KEY_CLIENTID] == $params[Enum::FIXED_KEY_CLIENTID] && isset($params[Enum::FIXED_KEY_QRCODEID]) && $data[Enum::FIXED_KEY_QRCODEID] == $params[Enum::FIXED_KEY_QRCODEID]) {
                    //通知页面扫码结果
                    // if (true) {
                        if ($this->pushWebMsg($params[Enum::FIXED_KEY_CLIENTID], $params[Enum::FIXED_KEY_QRCODEID], Enum::QR_STATE_SCANED, $params)) {
                        $result = ['scan_state' => 0, 'scan_msg' => '远程通知成功.'.Enum::QR_STATE_SCANED];
                    }
                }
            }
        }
        unset($data);
        return $result;
        // return [
        //     'scan_state' => 21,
        //     'scan_msg' => '未能识别扫码内容'
        // ];
    }

    /**
     * ST4-confirm.下发扫码完成“确认授权”事件
     * 
     * @param string $clientId  为socket标识
     * @param string $qrcodeId 手机最后扫码的那一个码ID
     * @param string $event 约定的事件类型匹配
     * @param array $params [text,time]
     * @param string|integer $scanUserId 扫码人的身份标识（前序流程已约定值）
     * 
     * @return array|null
     */
    public function scanEventConfirm(string $clientId, string $qrcodeId, string $event, array &$params = [], $scanUserId = 0)
    {
        $params = array_merge([
            Enum::FIXED_KEY_EVENT_NAME => $event,
            Enum::FIXED_KEY_UID => 0,
            Enum::FIXED_KEY_CLIENTID => $clientId,
            Enum::FIXED_KEY_QRCODEID => $qrcodeId,
            'text' => '',
        ], $params);
        if ($params[Enum::FIXED_KEY_EVENT_NAME] && $params[Enum::FIXED_KEY_EVENT_NAME] == $event && $params[Enum::FIXED_KEY_UID] == $scanUserId) {
            // $parmas[Enum::FIXED_KEY_UID] = $scanUserId;

            $state = $this->pushWebMsg($params[Enum::FIXED_KEY_CLIENTID], $params[Enum::FIXED_KEY_QRCODEID], Enum::QR_STATE_CONFIRM, [
                Enum::FIXED_KEY_CLIENTID => $clientId,
                Enum::FIXED_KEY_QRCODEID => $qrcodeId,
                Enum::FIXED_KEY_EVENT_NAME => $event,
                'time' => $params[Enum::FIXED_KEY_IMAGE_CTIME],
                'cryptText' => $this->pushTextRaw
            ]);
            if ($state) {
                $this->pushTextRaw = null;
                return ['scan_state' => 0, 'scan_msg' => '远程通知成功.'.Enum::QR_STATE_CONFIRM];
            }
        }
        $this->pushTextRaw = null;
        return ['scan_state' => 30, 'scan_msg' => '远程通知失败.'.Enum::QR_STATE_CONFIRM];
    }
    /**
     * ST4-reject.下发扫码完成“拒绝授权”事件
     * 
     * @param string $clientId  为socket标识
     * @param string $qrcodeId 手机最后扫码的那一个码ID
     * @param string $event 约定的事件类型匹配
     * @param array $params [text,time]
     * @param string|integer $scanUserId 扫码人的身份标识（前序流程已约定值）
     * 
     * @return array|null
     */
    public function scanEventReject(string $clientId, string $qrcodeId, string $event, array &$params = [], $scanUserId = 0)
    {
        $params = array_merge([
            Enum::FIXED_KEY_EVENT_NAME => $event,
            Enum::FIXED_KEY_UID => 0,
            Enum::FIXED_KEY_CLIENTID => $clientId,
            Enum::FIXED_KEY_QRCODEID => $qrcodeId,
            'text' => '',
        ], $params);
        if ($params[Enum::FIXED_KEY_EVENT_NAME] && $params[Enum::FIXED_KEY_EVENT_NAME] == $event && $params[Enum::FIXED_KEY_UID] == $scanUserId) {
            // $parmas[Enum::FIXED_KEY_UID] = $scanUserId;

            $state = true;
            $state = $this->pushWebMsg($params[Enum::FIXED_KEY_CLIENTID], $params[Enum::FIXED_KEY_QRCODEID], 'reject', [
                Enum::FIXED_KEY_CLIENTID => $clientId,
                Enum::FIXED_KEY_QRCODEID => $qrcodeId,
                Enum::FIXED_KEY_EVENT_NAME => $event,
                'time' => $params[Enum::FIXED_KEY_IMAGE_CTIME],
                'formData' => $params['text']
            ]);
            if ($state) {
                return ['scan_state' => 0, 'scan_msg' => '远程通知成功.'.Enum::QR_STATE_CONFIRM];
            }
        }
        return ['scan_state' => 30, 'scan_msg' => '远程通知失败.'.Enum::QR_STATE_CONFIRM];
    }
    
    /**
     * ST5.服务端接口解码
     * 
     * @param string $text  cryptText
     * @param string $event 约定的事件类型匹配 为null时，自动查询text范围
     * 
     * @return array|string
     */
    static function decryptFormData(string $text, string $event = null)
    {
        if($event === null){
            // if(substr($text, 0, 3) != 'ev.'){
            //     return [];
            // }
            list($prefix, $cryptText,) = explode('|', $text .'|||');
            $event = substr($prefix, 3);
            $text = $cryptText;
        }
        return self::qrcodeScan($text, $event);
    }
}